package com.api.rc.service.impl;

import com.api.rc.dao.*;
import com.api.rc.enums.*;
import com.api.rc.form.CreateRoomForm;
import com.api.rc.form.GameResultForm;
import com.api.rc.lobby.LobbyManager;
import com.api.rc.lobby.domain.LobbyRoom;
import com.api.rc.model.*;
import com.api.rc.model.temp.HostMatchingHistory;
import com.api.rc.model.temp.PlayerMatchHistory;
import com.api.rc.service.HostService;
import com.api.rc.service.RapidChallengeService;
import com.api.rc.service.RuleService;
import com.api.rc.vo.*;
import com.api.user.enums.GameEnum;
import com.api.user.exception.CoinChangedException;
import com.api.user.exception.CoinNotEnoughException;
import com.api.user.model.CoinTransaction;
import com.api.user.service.AchievementService;
import com.api.user.service.CoinTransactionService;
import com.api.user.service.GameService;
import com.api.user.service.TaskService;
import com.api.user.vo.GameVo;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * @author RenQiang
 * @date 2021/7/12
 */
@Service
@Transactional(rollbackFor = Exception.class)
public class RapidChallengeServiceImpl implements RapidChallengeService {
    private final GameService gameService;
    private final LobbyManager lobbyManager;
    private final HostService hostService;
    private final RoomMapper roomMapper;
    private final PasswordEncoder passwordEncoder;
    private final CoinTransactionService coinTransactionService;
    private final RoomTeamMapper roomTeamMapper;
    private final RoomMatchMapper roomMatchMapper;
    private final RoomPlayerMapper roomPlayerMapper;
    private final StandingMapper standingMapper;
    private final TaskService taskService;
    private final AchievementService achievementService;

    public RapidChallengeServiceImpl(GameService gameService, LobbyManager lobbyManager, HostService hostService, RoomMapper roomMapper, PasswordEncoder passwordEncoder, CoinTransactionService coinTransactionService, RoomTeamMapper roomTeamMapper, RoomMatchMapper roomMatchMapper, RoomPlayerMapper roomPlayerMapper, StandingMapper standingMapper, TaskService taskService, AchievementService achievementService) {
        this.gameService = gameService;
        this.lobbyManager = lobbyManager;
        this.hostService = hostService;
        this.roomMapper = roomMapper;
        this.passwordEncoder = passwordEncoder;
        this.coinTransactionService = coinTransactionService;
        this.roomTeamMapper = roomTeamMapper;
        this.roomMatchMapper = roomMatchMapper;
        this.roomPlayerMapper = roomPlayerMapper;
        this.standingMapper = standingMapper;
        this.taskService = taskService;
        this.achievementService = achievementService;
    }

    @Override
    public void createRoom(Long userId, CreateRoomForm createRoomForm) {
        HostOverviewVo hostOverviewVo = hostService.getOverview(userId);
        Preconditions.checkArgument(hostOverviewVo.getStatus() != HostStatus.BLOCKED, "主持人被禁止创建房间");
        this.checkRoom(hostOverviewVo, createRoomForm);

        Room room = new Room();
        room.setHostId(hostOverviewVo.getHostId());
        room.setGameId(createRoomForm.getGameId());
        room.setStatus(RoomStatus.WaitingToStart);
        room.setIsPublic(createRoomForm.getIsPublic());
        if (!room.getIsPublic()) {
            Preconditions.checkArgument(StringUtils.isNoneBlank(createRoomForm.getPassword()), "房间密码不能为空");
            room.setPassword(passwordEncoder.encode(createRoomForm.getPassword()));
        }
        room.setName(createRoomForm.getRoomName());
        room.setPlanningPlayTime(createRoomForm.getPlanningPlayTime());
        room.setGameMode(createRoomForm.getModel());
        room.setParticipants(createRoomForm.getParticipants());
        room.setFee(createRoomForm.getRoomFee());
        room.setBestOf(createRoomForm.getBestOf());
        Date now = new Date();
        room.setCreateTime(now);
        room.setUpdateTime(now);
        try {
            roomMapper.insert(room);
        } catch (DuplicateKeyException e) {
            throw new IllegalArgumentException("房间名称已存在", e);
        }
        List<RoomMatch> roomMatches = IntStream.rangeClosed(1, createRoomForm.getBestOf())
                .mapToObj(no -> {
                    RoomMatch roomMatch = new RoomMatch();
                    roomMatch.setRoomId(room.getId());
                    roomMatch.setNo(no);
                    roomMatch.setCreateTime(now);
                    roomMatch.setUpdateTime(now);
                    return roomMatch;
                })
                .collect(Collectors.toList());
        roomMatchMapper.insertList(roomMatches);

        GameEnum gameEnum = gameService.getGameEnumById(createRoomForm.getGameId());
        Host host = hostService.findById(room.getHostId());
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
            @Override
            public void afterCommit() {
                lobbyManager.createRoom(room.getId(), host.getUserId(), gameEnum.calcTeamCount(room.getParticipants()), gameEnum.calcPlayerPerTeam(room.getParticipants()));
            }
        });
    }

    @Override
    public void startGame(Long roomId) throws CoinChangedException {
        LobbyRoom lobbyRoom = lobbyManager.getLobbyRoom(roomId);
        Preconditions.checkArgument(lobbyRoom != null, "房间不存在或游戏已经开始");

        // 校验玩家状态
        Preconditions.checkArgument(lobbyRoom.isReady(), "还有玩家未准备");
        // 减扣coin
        Room room = roomMapper.selectByPrimaryKey(roomId);
        long currentTimeMillis = System.currentTimeMillis();
        List<CoinTransaction> coinTransactions = lobbyRoom.getTeamMap().values().stream()
                .flatMap(lobbyTeam -> lobbyTeam.getMemberMap().values().stream())
                .map(lobbyPlayer -> {
                    CoinTransaction coinTransaction = new CoinTransaction();
                    coinTransaction.setFromUserId(lobbyPlayer.getUserInfo().getId());
                    coinTransaction.setAmountBalance(room.getFee().getFee());
                    coinTransaction.setOrderId(roomId);
                    coinTransaction.setTransactionTime(currentTimeMillis);
                    coinTransaction.setTransactionType(12);
                    coinTransaction.setRemark("参加快速挑战");
                    return coinTransaction;
                })
                .collect(Collectors.toList());
        try {
            coinTransactionService.batchTransaction(coinTransactions);
        } catch (CoinNotEnoughException e) {
            throw new IllegalArgumentException("玩家金币不足: " + e.getUserIds());
        }

        //保存队伍信息
        List<RoomTeam> roomTeams = lobbyRoom.getTeamMap().values().stream()
                .map(lobbyTeam -> {
                    RoomTeam roomTeam = new RoomTeam();
                    roomTeam.setRoomId(roomId);
                    roomTeam.setNo(lobbyTeam.getNo());
                    return roomTeam;
                })
                .collect(Collectors.toList());
        roomTeamMapper.insertList(roomTeams);

        //保存玩家信息
        Date now = new Date(currentTimeMillis);
        List<RoomPlayer> roomPlayers = lobbyRoom.getTeamMap().values().stream()
                .flatMap(lobbyTeam -> lobbyTeam.getMemberMap().values().stream()
                        .map(lp -> {
                            RoomPlayer roomPlayer = new RoomPlayer();
                            roomPlayer.setRoomId(roomId);
                            roomPlayer.setTeamNo(lobbyTeam.getNo());
                            roomPlayer.setPlayerId(lp.getUserInfo().getId());
                            roomPlayer.setGameStatus(PlayerGameStatus.ToUpdate);
                            roomPlayer.setCreateTime(now);
                            roomPlayer.setUpdateTime(now);
                            return roomPlayer;
                        }))
                .collect(Collectors.toList());
        roomPlayerMapper.insertList(roomPlayers);

        //更新房间状态
        room.setStatus(RoomStatus.ToUpdate);
        room.setUpdateTime(now);
        roomMapper.updateByPrimaryKey(room);
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
            @Override
            public void afterCommit() {
                lobbyManager.destroyRoom(room.getId(), "游戏已开始");
            }
        });
    }

    @Override
    public boolean checkRoomPassword(Long roomId, String password) {
        Room room = roomMapper.selectByPrimaryKey(roomId);
        if (room == null) {
            throw new IllegalArgumentException("房间不存在");
        }
        if (room.getIsPublic()) {
            return true;
        }
        if (StringUtils.isEmpty(password)) {
            return false;
        }
        return passwordEncoder.matches(password, room.getPassword());
    }

    @Override
    public PageInfo<RoomVo> listByPage(Map<String, Object> queryParams, Integer page, Integer size) {
        PageHelper.startPage(page, size);
        List<RoomVo> roomVos = roomMapper.list(queryParams);
        PageInfo<RoomVo> pageInfo = new PageInfo<>(roomVos);
        if (CollectionUtils.isEmpty(pageInfo.getList())) {
            return pageInfo;
        }

        List<Long> hostIds = roomVos.stream()
                .map(roomVo -> roomVo.getHost().getHostId())
                .distinct()
                .collect(Collectors.toList());
        Map<Long, HostOverviewVo> hostOverviewVoMap = hostService.batchGetOverview(hostIds);

        Set<Long> gameIds = roomVos.stream()
                .map(RoomVo::getGameId)
                .collect(Collectors.toSet());
        Map<Long, GameVo> gameIdMap = gameService.getIdVoMap(gameIds);

        for (RoomVo roomVo : roomVos) {
            roomVo.setHost(hostOverviewVoMap.get(roomVo.getHost().getHostId()));
            roomVo.setFee(roomVo.getRoomFee().getFee());
            if (roomVo.getRoomStatus() == RoomStatus.WaitingToStart) {
                LobbyRoom lobbyRoom = lobbyManager.getLobbyRoom(roomVo.getRoomId());
                roomVo.setPlayers(Optional.ofNullable(lobbyRoom)
                        .map(LobbyRoom::getAllPlayers)
                        .map(List::size)
                        .orElse(0));
            } else {
                roomVo.setPlayers(roomVo.getCapacity());
            }
            GameEnum gameEnum = gameService.getGameEnumById(roomVo.getGameId());
            roomVo.setTeamsCount(gameEnum.calcTeamCount(roomVo.getCapacity()));
            roomVo.setGame(gameIdMap.get(roomVo.getGameId()));
        }
        return pageInfo;
    }

    @Override
    public void completeGame(GameResultForm gameResultForm) throws CoinChangedException {
        Room room = roomMapper.selectByPrimaryKey(gameResultForm.getRoomId());
        Preconditions.checkArgument(room != null, "房间ID不存在");

        GameEnum gameEnum = gameService.getGameEnumById(room.getGameId());
        Map<Integer, RoomMatch> roomMatchesMap = getMatchesMapByRoomId(gameResultForm.getRoomId());
        Map<Integer, RoomTeam> roomTeamsMap = getTeamsMapByRoomId(gameResultForm.getRoomId());
        this.checkGameResult(room, gameResultForm, roomMatchesMap, roomTeamsMap, gameEnum);

        Date now = new Date();
        for (GameResultForm.MatchResult matchResult : gameResultForm.getMatchResults()) {
            RoomMatch roomMatch = roomMatchesMap.get(matchResult.getMatchNo());
            roomMatch.setStandingSnapshot(matchResult.getStandingSnapshot());
            roomMatch.setUpdateTime(now);
            roomMatchMapper.updateByPrimaryKey(roomMatch);
        }

        List<Standing> standings = gameResultForm.getMatchResults().stream()
                .flatMap(matchResult -> {
                    List<Standing> matchStandings = matchResult.getTeamStandings().stream()
                            .map(teamStanding -> {
                                Standing standing = new Standing();
                                standing.setRoomId(room.getId());
                                standing.setTeamNo(teamStanding.getTeamNo());
                                standing.setMatchNo(matchResult.getMatchNo());
                                if (gameEnum == GameEnum.PUBG) {
                                    standing.setKillPoints(calcPubgKillPoints(teamStanding.getKills()));
                                    standing.setPlacePoints(calcPubgPlacePoints(teamStanding.getPlace()));
                                    standing.setTotalPoints(standing.getKillPoints() + standing.getPlacePoints());
                                    standing.setIsWinner(false);
                                } else {
                                    standing.setIsWinner(teamStanding.getIsWinner());
                                    standing.setTotalPoints(teamStanding.getIsWinner() ? 1 : 0);
                                }
                                standing.setCreateTime(now);
                                return standing;
                            })
                            .collect(Collectors.toList());
                    matchStandings.stream()
                            .max(Comparator.comparing(Standing::getTotalPoints))
                            .ifPresent(standing -> standing.setIsWinner(true));
                    return matchStandings.stream();
                })
                .collect(Collectors.toList());
        standingMapper.insertList(standings);

        Map<Integer, Integer> roomTeamNoTotalPointsMap = standings.stream()
                .collect(Collectors.groupingBy(Standing::getTeamNo, Collectors.summingInt(Standing::getTotalPoints)));
        List<Integer> pointsRank = roomTeamNoTotalPointsMap.values().stream()
                .sorted(Comparator.reverseOrder())
                .collect(Collectors.toList());
        for (RoomTeam roomTeam : roomTeamsMap.values()) {
            roomTeam.setRank(pointsRank.indexOf(roomTeamNoTotalPointsMap.get(roomTeam.getNo())) + 1);
            roomTeamMapper.updateByPrimaryKey(roomTeam);
        }

        this.coinsSettlement(room, gameEnum, new ArrayList<>(roomTeamsMap.values()), now);
        this.updateTaskAndAchievement(room.getId(), room.getHostId());
    }

    /**
     * 更新任务和徽章
     * 任务:
     * 1. 完成1次对打
     * 2. 完成3次对打
     * 3. 创建一次对打
     * 徽章:
     * 1. 通过快速挑战获得80000枚硬币
     * 2. 连续10天与朋友一起进行快速挑战
     * 3. 与朋友一起玩，赢得20个快速挑战
     * 4. 以五星评级主办30场比赛
     *
     * @param roomId
     * @param hostId
     */
    private void updateTaskAndAchievement(Long roomId, Long hostId) {
        /*
         * 获取所需数据
         * 1. 该房间所有玩家的对局历史, 所需字段: [玩家用户ID, 获取金币(是否胜利), 对局时间]
         * 2. 主持人所有创建房间历史, 所需字段: [主持人用户ID, 房间ID, 评分]
         */
        List<PlayerMatchHistory> playerMatchHistories = roomPlayerMapper.findPlayerHistoryByRoomId(roomId);
        List<HostMatchingHistory> hostMatchingHistories = roomMapper.findHostHistoryByRoomId(hostId);

        // 任务
        // 1. 完成1次对打
        playerMatchHistories.stream()
                .map(PlayerMatchHistory::getPlayerId)
                .distinct()
                .forEach(playerId -> taskService.completeTask("2", playerId,1));
        // 2. 完成3次对打
        playerMatchHistories.stream()
                .collect(Collectors.groupingBy(PlayerMatchHistory::getPlayerId,
                        Collectors.collectingAndThen(Collectors.toList(), List::size)))
                .entrySet().stream()
                .filter(entry -> entry.getValue() <= 3)
                .forEach(entry -> taskService.completeTask("3", entry.getKey(),entry.getValue()));
        // 3. 创建一次对打
        hostMatchingHistories.stream()
                .findFirst()
                .ifPresent(host -> taskService.completeTask("4", host.getHostUserId(),1));
        // 徽章
        // 1. 通过快速挑战获得80000枚硬币
        playerMatchHistories.stream()
                .collect(Collectors.groupingBy(PlayerMatchHistory::getPlayerId,
                        Collectors.summingDouble(history -> history.getWinCoins().doubleValue())))
                .forEach((playerId, totalWinCoins) -> achievementService.addStep(4L, playerId, totalWinCoins.intValue()));
        // 2. 连续10天与朋友一起进行快速挑战
        Date tenDaysAgo = Date.from(LocalDate.now().plusDays(-9).atStartOfDay(ZoneId.systemDefault()).toInstant());
        playerMatchHistories.stream()
                .collect(Collectors.groupingBy(PlayerMatchHistory::getPlayerId))
                .entrySet().stream()
                .map(entry -> Pair.of(entry.getKey(), entry.getValue().stream()
                        .filter(history -> history.getPlayTime().compareTo(tenDaysAgo) >= 0)
                        .map(history -> history.getPlayTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate())
                        .distinct()
                        .count()))
                .forEach(pair -> achievementService.addStep(5L, pair.getLeft(), pair.getRight().intValue()));
        // 3. 与朋友一起玩，赢得20个快速挑战
        playerMatchHistories.stream()
                .collect(Collectors.groupingBy(PlayerMatchHistory::getPlayerId))
                .entrySet().stream()
                .map(entry -> Pair.of(entry.getKey(), entry.getValue().stream()
                        .filter(history -> history.getWinCoins().compareTo(BigDecimal.ZERO) > 0)
                        .count()))
                .forEach(pair -> achievementService.addStep(6L, pair.getLeft(), pair.getRight().intValue()));
        // 4. 以五星评级主办30场比赛
        long star5Matches = hostMatchingHistories.stream()
                .filter(history -> history.getHostStar() != null && history.getHostStar() >= 5)
                .count();
        achievementService.addStep(8L, hostMatchingHistories.get(0).getHostUserId(), Math.toIntExact(star5Matches));
    }

    @Override
    public GameResultForm getGameResult(Long roomId) {
        Map<Integer, RoomMatch> roomMatchesMap = getMatchesMapByRoomId(roomId);
        Standing sample = new Standing();
        sample.setRoomId(roomId);
        List<Standing> standings = standingMapper.select(sample);
        Map<Integer, List<Standing>> roomMatchNoStandingsMap = standings.stream()
                .collect(Collectors.groupingBy(Standing::getMatchNo));
        GameResultForm gameResult = new GameResultForm();
        gameResult.setRoomId(roomId);
        gameResult.setMatchResults(roomMatchesMap.values().stream()
                .map(roomMatch -> {
                    GameResultForm.MatchResult matchResult = new GameResultForm.MatchResult();
                    matchResult.setMatchNo(roomMatch.getNo());
                    matchResult.setStandingSnapshot(roomMatch.getStandingSnapshot());
                    if (!roomMatchNoStandingsMap.containsKey(roomMatch.getNo())) {
                        return matchResult;
                    }
                    matchResult.setTeamStandings(roomMatchNoStandingsMap.get(roomMatch.getNo()).stream()
                            .map(standing -> {
                                GameResultForm.Standing teamStanding = new GameResultForm.Standing();
                                teamStanding.setTeamNo(standing.getTeamNo());
                                teamStanding.setKillPoints(standing.getKillPoints());
                                teamStanding.setPlacePoints(standing.getPlacePoints());
                                teamStanding.setTotalPoints(standing.getTotalPoints());
                                teamStanding.setIsWinner(standing.getIsWinner());
                                return teamStanding;
                            })
                            .collect(Collectors.toList()));
                    return matchResult;
                })
                .collect(Collectors.toList()));
        return gameResult;
    }

    @Override
    public void confirmGameResult(Long userId, Long roomId, Integer starToHost) {
        Preconditions.checkArgument(starToHost >= 0 && starToHost <= 5, "主持人评分只能在0-5之间");
        Room room = roomMapper.selectByPrimaryKey(roomId);
        Preconditions.checkArgument(room != null, "房间不存在");

        RoomPlayer sample = new RoomPlayer();
        sample.setRoomId(roomId);
        sample.setPlayerId(userId);
        RoomPlayer roomPlayer = roomPlayerMapper.selectOne(sample);
        Preconditions.checkArgument(roomPlayer != null, "房间不存在该玩家");
        Preconditions.checkArgument(roomPlayer.getGameStatus() == PlayerGameStatus.ToAgree, "当前游戏状态不能确认成绩");

        roomPlayer.setStarToHost(starToHost);
        roomPlayer.setGameStatus(roomPlayer.getWinCoins().compareTo(BigDecimal.ZERO) > 0 ? PlayerGameStatus.Win : PlayerGameStatus.Lose);
        roomPlayer.setUpdateTime(new Date());
        roomPlayerMapper.updateByPrimaryKey(roomPlayer);

        roomMapper.calcAndUpdateRoomHostStar(roomId);
        hostService.calcAndUpdateHostLevel(room.getHostId());
    }

    @Override
    public RcOverviewVo getOverview() {
        return roomMapper.getOverview();
    }

    @Override
    public List<PlayerVo> getPlayerDetail(Long roomId) {
        return roomPlayerMapper.getPlayerDetail(roomId);
    }

    /**
     * Coins结算
     *
     * @param room
     * @param gameEnum
     * @param roomTeams
     * @param time
     */
    private void coinsSettlement(Room room, GameEnum gameEnum, List<RoomTeam> roomTeams, Date time) throws CoinChangedException {
        List<CoinTransaction> coinTransactions = new ArrayList<>();
        //主持人赚取的Coins
        List<HostLevelRuleVo> levelRules = hostService.getLevelRules();
        HostOverviewVo hostOverviewVo = hostService.batchGetOverview(Collections.singletonList(room.getHostId())).get(room.getHostId());
        Optional<BigDecimal> hostEarnRatioOpt = levelRules.stream()
                .filter(levelRule -> Objects.equals(levelRule.getLevel(), hostOverviewVo.getLevel()))
                .findFirst()
                .map(HostLevelRuleVo::getEarnCoinRatio);
        Preconditions.checkArgument(hostEarnRatioOpt.isPresent(), "主持人结算异常");
        BigDecimal coinsToHost = room.getFee().getRemainingCoins()
                .multiply(hostEarnRatioOpt.get())
                .multiply(new BigDecimal(room.getParticipants()));
        CoinTransaction hostCoinTransaction = new CoinTransaction();
        hostCoinTransaction.setOrderId(room.getId());
        hostCoinTransaction.setTransactionType(14);
        hostCoinTransaction.setRemark("快速挑战主持人奖励");
        hostCoinTransaction.setAmountBalance(coinsToHost);
        hostCoinTransaction.setTransactionTime(time.getTime());
        hostCoinTransaction.setToUserId(hostOverviewVo.getUserId());
        coinTransactions.add(hostCoinTransaction);
        //更新主持人概况
        hostService.increaseHostGames(room.getHostId(), room.getId(), coinsToHost);

        //分配给玩家的Coins
        BigDecimal coinsToPlayers = room.getFee().getRemainingCoins().multiply(new BigDecimal(room.getParticipants()));
        //能获得Coins的队伍
        Map<Integer, BigDecimal> roomTeamNoCoinsRatioMap = calcTeamCoinsRatio(gameEnum, roomTeams);
        Map<Integer, List<RoomPlayer>> teamNoPlayersMap = getTeamNoPlayersMapByRoomId(room.getId());
        Map<Integer, List<RoomPlayer>> playersToAllotCoins = teamNoPlayersMap.entrySet().stream()
                .filter(entry -> roomTeamNoCoinsRatioMap.containsKey(entry.getKey()))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
        List<RoomPlayer> winPlayers = playersToAllotCoins.values().stream()
                .flatMap(Collection::stream)
                .collect(Collectors.toList());
        List<CoinTransaction> playerCoinTransactions = playersToAllotCoins.entrySet().stream()
                .flatMap(entry -> {
                    BigDecimal coinsToEachPlayer = coinsToPlayers.multiply(roomTeamNoCoinsRatioMap.get(entry.getKey()))
                            .divide(new BigDecimal(entry.getValue().size()), 2, BigDecimal.ROUND_HALF_UP);
                    return entry.getValue().stream()
                            .map(player -> {
                                CoinTransaction playerCoinTransaction = new CoinTransaction();
                                playerCoinTransaction.setOrderId(room.getId());
                                playerCoinTransaction.setTransactionType(13);
                                playerCoinTransaction.setRemark("快速挑战获得奖励");
                                playerCoinTransaction.setAmountBalance(coinsToEachPlayer);
                                playerCoinTransaction.setTransactionTime(time.getTime());
                                playerCoinTransaction.setToUserId(player.getPlayerId());
                                return playerCoinTransaction;
                            });
                })
                .collect(Collectors.toList());
        coinTransactions.addAll(playerCoinTransactions);
        try {
            coinTransactionService.batchTransaction(coinTransactions);
        } catch (CoinNotEnoughException e) {
            throw new IllegalArgumentException("玩家金币不足: " + e.getUserIds());
        }

        //保存玩家赢得Coins
        Map<Long, BigDecimal> userIdWinCoinsMap = playerCoinTransactions.stream()
                .collect(Collectors.toMap(CoinTransaction::getToUserId, CoinTransaction::getAmountBalance));
        for (RoomPlayer winPlayer : winPlayers) {
            winPlayer.setGameStatus(PlayerGameStatus.ToAgree);
            winPlayer.setUpdateTime(time);
            winPlayer.setWinCoins(userIdWinCoinsMap.get(winPlayer.getPlayerId()));
            roomPlayerMapper.updateByPrimaryKey(winPlayer);
        }
        List<RoomPlayer> losePlayers = teamNoPlayersMap.entrySet().stream()
                .filter(entry -> !roomTeamNoCoinsRatioMap.containsKey(entry.getKey()))
                .flatMap(entry -> entry.getValue().stream())
                .collect(Collectors.toList());
        for (RoomPlayer losePlayer : losePlayers) {
            losePlayer.setGameStatus(PlayerGameStatus.ToAgree);
            losePlayer.setUpdateTime(time);
            losePlayer.setWinCoins(BigDecimal.ZERO);
            roomPlayerMapper.updateByPrimaryKey(losePlayer);
        }

        //保存平台回报Coins
        room.setHostEarnCoins(coinsToHost);
        BigDecimal coinsToPlatform = room.getFee().getAbsorbCoins().multiply(new BigDecimal(room.getParticipants()));
        room.setPlatformRewardCoins(coinsToPlatform.subtract(coinsToHost));
        room.setStatus(RoomStatus.Complete);
        room.setUpdateTime(time);
        roomMapper.updateByPrimaryKey(room);
    }

    private Map<Integer, List<RoomPlayer>> getTeamNoPlayersMapByRoomId(Long roomId) {
        RoomPlayer sample = new RoomPlayer();
        sample.setRoomId(roomId);
        List<RoomPlayer> roomPlayers = roomPlayerMapper.select(sample);
        return roomPlayers.stream()
                .collect(Collectors.groupingBy(RoomPlayer::getTeamNo));
    }

    private static final Map<Integer, BigDecimal> PUBG_RANK_COINS_RATIO = ImmutableMap.<Integer, BigDecimal>builder()
            .put(1, new BigDecimal("0.4")).put(2, new BigDecimal("0.2")).put(3, new BigDecimal("0.15"))
            .put(4, new BigDecimal("0.1")).put(5, new BigDecimal("0.05")).put(6, new BigDecimal("0.05"))
            .put(7, new BigDecimal("0.025")).put(8, new BigDecimal("0.025"))
            .build();

    /**
     * 根据战队名次计算Coins分配比例
     *
     * @param gameEnum
     * @param roomTeams
     * @return
     */
    private static Map<Integer, BigDecimal> calcTeamCoinsRatio(GameEnum gameEnum, List<RoomTeam> roomTeams) {
        if (gameEnum == GameEnum.PUBG) {
            return roomTeams.stream()
                    .filter(roomTeam -> PUBG_RANK_COINS_RATIO.containsKey(roomTeam.getRank()))
                    .map(roomTeam -> Pair.of(roomTeam.getNo(), PUBG_RANK_COINS_RATIO.get(roomTeam.getNo())))
                    .collect(Collectors.toMap(Pair::getLeft, Pair::getRight));
        } else {
            return roomTeams.stream()
                    .filter(roomTeam -> roomTeam.getRank() == 1)
                    .collect(Collectors.toMap(RoomTeam::getNo, roomTeam -> new BigDecimal("1")));
        }
    }

    private static final Map<Integer, Integer> PUBG_PLACE_POINTS_RATIO = ImmutableMap.<Integer, Integer>builder()
            .put(1, 15).put(2, 12).put(3, 10).put(4, 8).put(5, 6).put(6, 4).put(7, 2).put(8, 1).put(9, 1).put(10, 1).put(11, 1).put(12, 1)
            .build();

    /**
     * 计算PUBG排名分
     *
     * @param place
     * @return
     */
    private static int calcPubgPlacePoints(int place) {
        return PUBG_PLACE_POINTS_RATIO.getOrDefault(place, 0);
    }

    /**
     * 计算PUBG淘汰分
     *
     * @param kills
     * @return
     */
    private static int calcPubgKillPoints(int kills) {
        return kills;
    }

    private void checkGameResult(Room room, GameResultForm gameResultForm, Map<Integer, RoomMatch> roomMatchesMap, Map<Integer, RoomTeam> roomTeamsMap, GameEnum gameEnum) {
        Preconditions.checkArgument(room.getStatus() == RoomStatus.ToUpdate, "游戏未开始或已结束");
        Preconditions.checkArgument(gameResultForm.getMatchResults().stream().map(GameResultForm.MatchResult::getMatchNo).distinct().count() == gameResultForm.getMatchResults().size(),
                "比赛场次不能重复");
        Preconditions.checkArgument(gameResultForm.getMatchResults().size() <= room.getBestOf(), String.format("BO%d最多上传%d场比赛结果", room.getBestOf(), room.getBestOf()));
        Preconditions.checkArgument(gameResultForm.getMatchResults().size() >= room.getBestOf() / 2 + 1, String.format("BO%d至少上传%d场比赛结果", room.getBestOf(), room.getBestOf() / 2 + 1));
        for (GameResultForm.MatchResult matchResult : gameResultForm.getMatchResults()) {
            Preconditions.checkArgument(roomMatchesMap.containsKey(matchResult.getMatchNo()), String.format("比赛场次%d不存在", matchResult.getMatchNo()));
            Preconditions.checkArgument(matchResult.getTeamStandings().stream().map(GameResultForm.Standing::getTeamNo).distinct().count() == roomTeamsMap.size(),
                    String.format("每场比赛请上传%d个队伍的所有成绩", roomTeamsMap.size()));
            for (GameResultForm.Standing teamStanding : matchResult.getTeamStandings()) {
                Preconditions.checkArgument(roomTeamsMap.containsKey(teamStanding.getTeamNo()), String.format("队伍编号%d不存在", teamStanding.getTeamNo()));
                if (gameEnum == GameEnum.PUBG) {
                    Preconditions.checkArgument(teamStanding.getKills() != null, "PUBG killPoints不能为空");
                    Preconditions.checkArgument(teamStanding.getPlace() != null, "PUBG placePoints不能为空");
                } else {
                    Preconditions.checkArgument(teamStanding.getIsWinner() != null, "isWinner不能为空");
                }
            }
        }
        if (gameEnum == GameEnum.PUBG) {
            Preconditions.checkArgument(room.getBestOf() == gameResultForm.getMatchResults().size(), String.format("PUBG BO%d请上传%d场比赛结果", room.getBestOf(), room.getBestOf()));
        } else {
            Map<Integer, Integer> teamTotalScoreMap = gameResultForm.getMatchResults().stream()
                    .flatMap(matchResult -> matchResult.getTeamStandings().stream())
                    .collect(Collectors.groupingBy(GameResultForm.Standing::getTeamNo, Collectors.summingInt(teamStanding -> teamStanding.getIsWinner() ? 1 : 0)));
            // 校验每个队伍分数不一样
            Preconditions.checkArgument(teamTotalScoreMap.size() == new HashSet<>(teamTotalScoreMap.values()).size(), "根据当前结果无法决出名次，请上传更多比赛结果");
        }
    }

    private Map<Integer, RoomTeam> getTeamsMapByRoomId(Long roomId) {
        RoomTeam sample = new RoomTeam();
        sample.setRoomId(roomId);
        List<RoomTeam> roomTeams = roomTeamMapper.select(sample);
        return roomTeams.stream()
                .collect(Collectors.toMap(RoomTeam::getNo, Function.identity()));
    }

    private Map<Integer, RoomMatch> getMatchesMapByRoomId(Long roomId) {
        RoomMatch sample = new RoomMatch();
        sample.setRoomId(roomId);
        List<RoomMatch> roomMatches = roomMatchMapper.select(sample);
        return roomMatches.stream()
                .collect(Collectors.toMap(RoomMatch::getNo, Function.identity()));
    }

    /**
     * 规则检查
     *
     * @param hostOverviewVo
     * @param form
     */
    private void checkRoom(HostOverviewVo hostOverviewVo, CreateRoomForm form) {
        Preconditions.checkArgument(hostOverviewVo.getStatus() == HostStatus.ENABLE, "当前用户不能创建房间");
        Preconditions.checkArgument(form.getPlanningPlayTime() == null || form.getPlanningPlayTime().after(new Date()), "游戏时间不能小于当前时间");
        GameEnum gameEnum = gameService.getGameEnumById(form.getGameId());
        Preconditions.checkArgument(gameEnum != null, "不支持该游戏");
        if (gameEnum == GameEnum.PUBG) {
            Preconditions.checkArgument(form.getParticipants() % 4 == 0, "PUBG玩家数量只能是4的倍数");
            Preconditions.checkArgument(form.getParticipants() <= 100, "PUBG玩家数量不能超过100");
        } else {
            Preconditions.checkArgument(form.getParticipants() % 2 == 0, "玩家数量只能是2的倍数");
            Preconditions.checkArgument(gameEnum != GameEnum.DOTA2 || form.getParticipants() <= 10, "DOTA2玩家数量不能超过10");
            Preconditions.checkArgument(gameEnum != GameEnum.CS_GO || form.getParticipants() <= 30, "CS GO玩家数量不能超过30");
        }
        List<HostLevelRuleVo> hostLevelRuleVos = hostService.getLevelRules();
        boolean feeAvailable = hostLevelRuleVos.stream()
                .filter(hostLevelRuleVo -> Objects.equals(hostLevelRuleVo.getLevel(), hostOverviewVo.getLevel()))
                .anyMatch(hostLevelRuleVo -> hostLevelRuleVo.getAvailableRoomFees().contains(form.getRoomFee()));
        Preconditions.checkArgument(feeAvailable, hostOverviewVo.getLevel() + "级主持人不能选择房费" + form.getRoomFee().getFee().intValue());
        if (form.getParticipants() == 2) {
            Preconditions.checkArgument(form.getRoomFee().compareTo(RoomFee.ROOM_FEE_230) >= 0, String.format("1 VS 1房间只能选择%d以上的房费", RoomFee.ROOM_FEE_230.getFee().intValue()));
        }
    }
}
